package cn.dao.impl;

import cn.dao.NeoDao;
import cn.structure.ClassEntity;
import cn.structure.ConstructorCallEntity;
import cn.structure.ConstructorEntity;
import cn.structure.InvocationEntity;
import cn.structure.MethodEntity;
import cn.structure.TypeParameterEntity;
import cn.structure.VariableEntity;
import cn.structure.edp.EdpEntity;
import cn.structure.ipc.IpcEntity;
import cn.type.EntityTypeEnum;
import cn.type.RelTypeEnum;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import spoon.reflect.code.CtAbstractInvocation;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtTypeInformation;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.reference.CtExecutableReference;

import java.util.stream.Collectors;

public class NeoDaoImpl implements NeoDao {

    private static NeoDao instance = new NeoDaoImpl();

    public static NeoDao getInstance() {
        return instance;
    }

    @Override
    public Relationship createRelation(Node source, Node target, RelTypeEnum relType) {
        return source.createRelationshipTo(target, relType);
    }

    @Override
    public Node createClassNode(Transaction tx, ClassEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.ClassEntity.toString()));
        node.addLabel(Label.label(entity.getClass_type().toString()));
        node.setProperty("ast_path", getNotNull(entity.getAst_path()));
        node.setProperty("name", getNotNull(entity.getName()));
        node.setProperty("modifiers", getNotNull(entity.getModifiers()));
        node.setProperty("isInner", entity.isInner());
        node.setProperty("isShadow", entity.isShadow());
        node.setProperty("isParameterized", entity.isParameterized());
        node.setProperty("super_class", entity.getSuper_class() == null ? "" : entity.getSuper_class().getQualifiedName());
        node.setProperty("super_interfaces", entity
                .getSuper_interfaces()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toSet())
                .toString());
        node.setProperty("all_super_classes", entity
                .getAll_super_classes()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toSet())
                .toString());
        node.setProperty("all_super_interfaces", entity
                .getAll_super_interfaces()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toSet())
                .toString());
        node.setProperty("fields", entity
                .getFields()
                .stream()
                .map(CtVariable::toStringDebug)
                .collect(Collectors.toList())
                .toString());
        node.setProperty("methods", entity
                .getMethods()
                .stream()
                .map(CtExecutable::getSignature)
                .collect(Collectors.toSet())
                .toString());
        node.setProperty("constructors", entity
                .getConstructors()
                .stream()
                .map(CtExecutable::getSignature)
                .collect(Collectors.toSet())
                .toString());
        node.setProperty("type_parameters", entity
                .getType_parameters()
                .stream()
                .map(CtTypeParameter::toStringDebug)
                .collect(Collectors.toList())
                .toString());
        return node;
    }

    @Override
    public Node createVariableNode(Transaction tx, VariableEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.VariableEntity.toString()));
        node.addLabel(Label.label(entity.getScope().toString()));
        node.setProperty("ast_path", getNotNull(entity.getAst_path()));
        node.setProperty("name", getNotNull(entity.getName()));
        node.setProperty("isArray", entity.isArray());
        node.setProperty("origin", entity.getOrigin() == null ? "" : entity.getOrigin().getQualifiedName());
        node.setProperty("modifiers", getNotNull(entity.getModifiers()));
        node.setProperty("assignment", getNotNull(entity.getAssignment()));
        node.setProperty("container_items", entity
                .getContainer_items()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toList())
                .toString());
        return node;
    }

    @Override
    public Node createTypeParameterNode(Transaction tx, TypeParameterEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.TypeParameterEntity.toString()));
        node.setProperty("ast_path", getNotNull(entity.getAst_path()));
        node.setProperty("name", getNotNull(entity.getName()));
        {
            CtElement parent = entity.getDeclaration_in();
            if (parent instanceof CtMethod) {
                CtMethod<?> parent_method = (CtMethod<?>) parent;
                node.setProperty("declaration_in", parent_method.getDeclaringType().getQualifiedName() + "." + parent_method.getSignature());
            } else if (parent instanceof CtTypeInformation) {
                node.setProperty("declaration_in", ((CtTypeInformation) parent).getQualifiedName());
            }
        }
        node.setProperty("super_class", entity.getSuper_class() == null ? "" : entity.getSuper_class().getQualifiedName());
        node.setProperty("super_interfaces", entity
                .getSuper_interfaces()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toSet())
                .toString());
        return node;
    }

    @Override
    public Node createMethodNode(Transaction tx, MethodEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.MethodEntity.toString()));
        node.setProperty("ast_path", getNotNull(entity.getAst_path()));
        node.setProperty("full_signature", getNotNull(entity.getFull_signature()));
        node.setProperty("modifiers", getNotNull(entity.getModifiers()));
        node.setProperty("declaration_class", entity.getDeclaration_class() == null ? "" : entity.getDeclaration_class().getQualifiedName());
        node.setProperty("parameters", entity
                .getParameters()
                .stream()
                .map(CtVariable::toStringDebug)
                .collect(Collectors.toList())
                .toString());
        if (entity.getLocal_variables() != null) {
            node.setProperty("local_variables", entity
                    .getLocal_variables()
                    .stream()
                    .map(CtVariable::toStringDebug)
                    .collect(Collectors.toList())
                    .toString());
        }
        if (entity.getInvocations() != null) {
            node.setProperty("invocations", entity
                    .getInvocations()
                    .stream()
                    .map(CtAbstractInvocation::toStringDebug)
                    .collect(Collectors.toList())
                    .toString());
        }
        // 以上为通用属性
        node.setProperty("name", getNotNull(entity.getName()));
        node.setProperty("signature", getNotNull(entity.getSignature()));
        node.setProperty("return_type", entity.getReturn_type() == null ? "" : entity.getReturn_type().getQualifiedName());
        node.setProperty("actual_return_type", entity.getActual_return_type() == null ? "" : entity.getActual_return_type().getQualifiedName());
        node.setProperty("type_parameters", entity
                .getType_parameters()
                .stream()
                .map(CtTypeParameter::toStringDebug)
                .collect(Collectors.toList())
                .toString());
        node.setProperty("isGetter", entity.isGetter());
        node.setProperty("isSetter", entity.isSetter());
        node.setProperty("override_classes", entity
                .getOverride_classes()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toSet())
                .toString());
        return node;
    }

    @Override
    public Node createConstructorNode(Transaction tx, ConstructorEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.ConstructorEntity.toString()));
        node.setProperty("ast_path", getNotNull(entity.getAst_path()));
        node.setProperty("full_signature", getNotNull(entity.getFull_signature()));
        node.setProperty("modifiers", getNotNull(entity.getModifiers()));
        node.setProperty("declaration_class", entity.getDeclaration_class() == null ? "" : entity.getDeclaration_class().getQualifiedName());
        node.setProperty("parameters", entity
                .getParameters()
                .stream()
                .map(CtVariable::toStringDebug)
                .collect(Collectors.toList())
                .toString());
        node.setProperty("local_variables", entity
                .getLocal_variables()
                .stream()
                .map(CtVariable::toStringDebug)
                .collect(Collectors.toList())
                .toString());
        node.setProperty("invocations", entity
                .getInvocations()
                .stream()
                .map(CtAbstractInvocation::toStringDebug)
                .collect(Collectors.toList())
                .toString());
        return node;
    }

    @Override
    public Node createInvocationNode(Transaction tx, InvocationEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.InvocationEntity.toString()));
        node.setProperty("ast_path", getNotNull(entity.getAst_path()));
        node.setProperty("code", getNotNull(entity.getCode()));
        {
            CtElement parent = entity.getDeclaration_in();
            if (parent instanceof CtMethod) {
                CtMethod<?> parent_method = (CtMethod<?>) parent;
                node.setProperty("declaration_in", parent_method.getDeclaringType().getQualifiedName() + "." + parent_method.getSignature());
            } else if (parent instanceof CtConstructor) {
                node.setProperty("declaration_in", ((CtConstructor<?>) parent).getSignature());
            } else if (parent instanceof CtVariable) {
                node.setProperty("declaration_in", parent.toStringDebug());
            }
        }
        {
            CtExecutableReference<?> called_executable = entity.getCalled_executable();
            if (called_executable != null && called_executable.getDeclaringType() != null) {
                String called_class_name = called_executable.getDeclaringType().getQualifiedName();
                node.setProperty("called_class", called_class_name);
                node.setProperty("called_executable", called_class_name + "." + called_executable.getSignature());
            }
        }
        node.setProperty("arguments", entity
                .getArgument_types()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toList())
                .toString());
        node.setProperty("next", entity.getNext() == null ? "" : entity.getNext().toStringDebug());
        // 以上为通用属性
        node.setProperty("caller_type", entity.getCallerType().toString());
        node.setProperty("caller_obj", entity.getCaller_obj() == null ? "" : entity.getCaller_obj().toStringDebug());
        return node;
    }

    @Override
    public Node createConstructorCallNode(Transaction tx, ConstructorCallEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.ConstructorCallEntity.toString()));
        node.setProperty("ast_path", getNotNull(entity.getAst_path()));
        node.setProperty("code", getNotNull(entity.getCode()));
        {
            CtElement parent = entity.getDeclaration_in();
            if (parent instanceof CtMethod) {
                CtMethod<?> parent_method = (CtMethod<?>) parent;
                node.setProperty("declaration_in", parent_method.getDeclaringType().getQualifiedName() + "." + parent_method.getSignature());
            } else if (parent instanceof CtConstructor) {
                node.setProperty("declaration_in", ((CtConstructor<?>) parent).getSignature());
            } else if (parent instanceof CtVariable) {
                node.setProperty("declaration_in", parent.toStringDebug());
            }
        }
        {
            CtExecutableReference<?> called_executable = entity.getCalled_executable();
            if (called_executable != null && called_executable.getDeclaringType() != null) {
                String called_class_name = called_executable.getDeclaringType().getQualifiedName();
                node.setProperty("called_class", called_class_name);
                node.setProperty("called_executable", called_class_name + "." + called_executable.getSignature());
            }
        }
        node.setProperty("arguments", entity
                .getArgument_types()
                .stream()
                .map(CtTypeInformation::getQualifiedName)
                .collect(Collectors.toList())
                .toString());
        node.setProperty("next", entity.getNext() == null ? "" : entity.getNext().toStringDebug());
        node.setProperty("caller_obj", entity.getCaller_obj() == null ? "" : entity.getCaller_obj().toStringDebug());
        return node;
    }

    @Override
    public Node createEdpNode(Transaction tx, EdpEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.EdpEntity.toString()));
        node.addLabel(Label.label(entity.getEntityType()));
        node.setProperty("name", entity.getEntityType());
        edp_node_id_map.put(entity, node.getId());
        return node;
    }

    @Override
    public Node createIpcNode(Transaction tx, IpcEntity entity) {
        Node node = tx.createNode(Label.label(EntityTypeEnum.IpcEntity.toString()));
        node.addLabel(Label.label(entity.getEntityType()));
        node.setProperty("name", entity.getEntityType());
        ipc_node_id_map.put(entity, node.getId());
        return node;
    }

    @Override
    public Node getNodeByProperty(Transaction tx, EntityTypeEnum entityType, String property_name, String property_val) {
        return tx.findNode(Label.label(entityType.toString()), property_name, property_val);
    }

    @Override
    public Node getNodeByAstPath(Transaction tx, String entity_type, String ast_path) {
        return tx.findNode(Label.label(entity_type), "ast_path", ast_path);
    }

    @Override
    public Node getClassNodeByName(Transaction tx, String qualified_name) {
        return getNodeByProperty(tx, EntityTypeEnum.ClassEntity, "qualified_name", qualified_name);
    }

    @Override
    public Node getMethodNodeBySignature(Transaction tx, String full_signature) {
        return getNodeByProperty(tx, EntityTypeEnum.MethodEntity, "full_signature", full_signature);
    }

    @Override
    public Node getConstructorNodeBySignature(Transaction tx, String full_signature) {
        return getNodeByProperty(tx, EntityTypeEnum.ConstructorEntity, "full_signature", full_signature);
    }

    private static String getNotNull(Object obj) {
        return obj == null ? "" : obj.toString();
    }
}
